﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using Microsoft.Cci;
using Logging;

using PartialExpressionCompleter.Util;
using PartialExpressionCompleter.Query;

namespace PartialExpressionCompleter.Experiment
{
    class AbstractTypeInferencer : CodeTraverser
    {
        private Dictionary<IMethodDefinition, List<TypeConstraint>> constraintsByFunc
            = new Dictionary<IMethodDefinition, List<TypeConstraint>>();
        private List<TypeConstraint> currentFuncConstraints;
        private HashSet<ILocalDefinition> currentFuncLocals;
        private Dictionary<IExpression, TypeTerm> currentFuncExps;
        private List<SavedExpression<IMethodCall>> calls = new List<SavedExpression<IMethodCall>>();
        private List<SavedExpression<IAssignment>> assignments = new List<SavedExpression<IAssignment>>();
        private List<SavedExpression<IExpression>> comparisons = new List<SavedExpression<IExpression>>();

        public AbstractTypeInferencer(IAssembly unit, bool displayTypeMeanings)
        {
            this.PreorderVisitor = new PreVisitor(this);
            this.PostorderVisitor = new PostVisitor(this);
            this.TraverseIntoMethodBodies = true;
            /*foreach (var method in CCIUtil.GetAllMethods())
            {
                this.Traverse(method);
            }*/
            this.Traverse(unit);
            if (displayTypeMeanings)
            {
                //DisplayTermMeanings();
            }
        }
        public AbstractTypeInferencer(IAssembly unit) : this(unit, true) { }

        /// <summary>
        /// If true, then don't allow type terms for this method.
        /// </summary>
        /// <param name="method"></param>
        /// <returns></returns>
        private static bool IsIgnoredMethod(IMethodReference method)
        {
            return method.ContainingType.Equals(CCIUtil.GetObjectType())
                || method.ContainingType.Equals(CCIUtil.GetStringType())
                || method.Name.ToString().Equals("ToString")
                || method.Name.ToString().Equals("Equals")
                || method.ContainingType.ToString().Equals("System.Threading.Interlocked")
                || method.ContainingType.ToString().StartsWith("System.Collections")
                || method.ContainingType.ToString().StartsWith("System.Linq")
                ;
        }
        private TypeTerm CreateFormalParameterTypeTerm(
                IMethodDefinition method, int index)
        {
            if (IsIgnoredMethod(method)
                || index == 0 && method.IsConstructor && method.HasThisParameter())
            {
                return new AnonymousTypeTerm();
            }
            else
            {
                return new FormalParameterTypeTerm(method, index);
            }
        }
        private TypeTerm CreateReturnTypeTypeTerm(IMethodDefinition method)
        {
            if (IsIgnoredMethod(method))
            {
                return new AnonymousTypeTerm();
            }
            else
            {
                return new ReturnTypeTypeTerm(method);
            }
        }

        private void RememberMethodCall(IMethodCall call)
        {
            calls.Add(new SavedExpression<IMethodCall>(currentMethod, call,
                currentFuncConstraints.ToList(), currentFuncExps,
                currentFuncLocals.ToList()));
        }
        private void RememberAssignment(IAssignment assignment)
        {
            assignments.Add(new SavedExpression<IAssignment>(currentMethod, assignment,
                currentFuncConstraints.ToList(), currentFuncExps,
                currentFuncLocals.ToList()));
        }
        private void RememberComparison(IMethodCall call)
        {
            comparisons.Add(new SavedExpression<IExpression>(currentMethod, call,
                currentFuncConstraints.ToList(), currentFuncExps,
                currentFuncLocals.ToList()));
        }
        private void RememberComparison(IBinaryOperation op)
        {
            comparisons.Add(new SavedExpression<IExpression>(currentMethod, op,
                currentFuncConstraints.ToList(), currentFuncExps,
                currentFuncLocals.ToList()));
        }
        public IEnumerable<SavedExpression<IMethodCall>> GetAllCalls()
        {
            return calls;
        }
        public IEnumerable<SavedExpression<IAssignment>> GetAllAssignments()
        {
            return assignments;
        }
        public IEnumerable<SavedExpression<IExpression>> GetAllComparisons()
        {
            return comparisons;
        }

        private IMethodDefinition currentMethod = null;

        private class PreVisitor : CodeVisitor
        {
            private readonly AbstractTypeInferencer ati;
            public PreVisitor(AbstractTypeInferencer ati)
            {
                this.ati = ati;
            }
            public override void Visit(IMethodBody methodBody)
            {
                ati.currentMethod = methodBody.MethodDefinition;
                ati.currentFuncConstraints = new List<TypeConstraint>();
                ati.currentFuncExps = new Dictionary<IExpression, TypeTerm>();
                ati.currentFuncLocals = new HashSet<ILocalDefinition>();
            }
            public override void Visit(ILocalDefinition local)
            {
                ati.currentFuncLocals.Add(local);
            }
            public override void VisitReference(ILocalDefinition local)
            {
                ati.currentFuncLocals.Add(local);
            }
        }
        private class PostVisitor : CodeVisitor
        {
            private readonly AbstractTypeInferencer ati;
            public PostVisitor(AbstractTypeInferencer ati)
            {
                this.ati = ati;
            }
            public override void Visit(IMethodBody methodBody)
            {
                if (ati.currentFuncConstraints.Count > 0)
                {
                    ati.constraintsByFunc[ati.currentMethod] = ati.currentFuncConstraints;
                }
                ati.currentFuncConstraints = null;
                ati.currentFuncExps = null;
                ati.currentFuncLocals = null;
                ati.currentMethod = null;
            }
            public override void Visit(IMethodCall methodCall)
            {
                ati.RememberMethodCall(methodCall);
                string name = methodCall.MethodToCall.Name.ToString();
                if (comparisonMethods.Contains(name))
                {
                    ati.RememberComparison(methodCall);
                }

                var method = methodCall.MethodToCall.ResolvedMethod;
                List<IExpression> args = new List<IExpression>();
                if (method.HasThisParameter())
                {
                    args.Add(methodCall.ThisArgument);
                }
                args.AddRange(methodCall.Arguments);
                if (IsIgnoredMethod(method))
                {
                    ati.RegisterConstraints(args.Select((arg, i) =>
                        ConstraintForAssignment(new AnonymousTypeTerm(),
                                                ati.GetTypeTermFor(arg))));
                }
                else
                {
                    ati.RegisterConstraints(args.Select((arg, i) =>
                        ConstraintForAssignment(method.IsConstructor && method.HasThisParameter() && i == 0
                            ? new AnonymousTypeTerm() as TypeTerm
                            : new FormalParameterTypeTerm(method, i),
                                                ati.GetTypeTermFor(arg))));
                }
            }
            public override void Visit(IAssignment assignment)
            {
                ati.RememberAssignment(assignment);

                ati.RegisterConstraint(ConstraintForAssignment(
                    ati.GetTypeTermForDefinition(assignment.Target.Definition),
                    ati.GetTypeTermFor(assignment.Source)));
            }
            public override void Visit(IReturnStatement returnStatement)
            {
                var exp = returnStatement.Expression;
                if (exp != null)
                {
                    ati.RegisterConstraint(ConstraintForAssignment(
                        ati.CreateReturnTypeTypeTerm(ati.currentMethod),
                        ati.GetTypeTermFor(exp)));
                }
            }
            public override void Visit(IConditional cond)
            {
                ati.RegisterConstraint(
                    ati.ConstraintForAssignment(cond.ResultIfTrue, cond.ResultIfFalse));
            }
            public override void Visit(ILessThan lessThan)
            {
                ati.RememberComparison(lessThan);
            }
            public override void Visit(ILessThanOrEqual lessThanOrEqual)
            {
                ati.RememberComparison(lessThanOrEqual);
            }
            public override void Visit(IGreaterThan greaterThan)
            {
                ati.RememberComparison(greaterThan);
            }
            public override void Visit(IGreaterThanOrEqual greaterThanOrEqual)
            {
                ati.RememberComparison(greaterThanOrEqual);
            }
        }
        private static readonly string[] comparisonMethods =
            new string[] { "op_LessThan", "op_LessThanOrEqual",
                           "op_GreaterThan", "op_GreaterThanOrEqual" };

        private TypeTerm GetTypeTermForDefinition(object def)
        {
            if (def is ILocalDefinition)
            {
                var loc = def as ILocalDefinition;
                // Not sure why this happens, but it seems rare...
                if (loc.MethodDefinition is Dummy)
                {
                    return new AnonymousTypeTerm();
                }
                else
                {
                    return new NamedTypeTerm(loc);
                }
            }
            else if (def is IFieldDefinition)
            {
                var field = def as IFieldDefinition;
                return field.AsTypeTerm();
            }
            else if (def is IFieldReference)
            {
                var field = def as IFieldReference;
                return field.AsTypeTerm();
            }
            else if (def is IParameterDefinition)
            {
                var param = def as IParameterDefinition;
                // TODO Index?
                // Isn't meth always currentMethod?
                var meth = param.ContainingSignature as IMethodDefinition;
                if (meth == null)
                {
                    throw new Exception();
                }
                return CreateFormalParameterTypeTerm(meth,
                    (meth.HasThisParameter() ? 1 : 0) + param.Index);
            }
            else if (def is IArrayIndexer)
            {
                var ind = def as IArrayIndexer;
                return new NamedTypeTerm(currentMethod,
                    GetTypeTermFor(ind.IndexedObject).ToString() + "[]");
            }
            else if (def is IAddressDereference)
            {
                var deref = def as IAddressDereference;
                return GetTypeTermFor(deref); // TODO Do anything else here?
            }
            else
            {
                throw new NotImplementedException("Unknown target definition type: "
                    + def.GetType());
            }
        }
        private TypeTerm GetTypeTermFor(IExpression exp)
        {
            TypeTerm res;
            if (!currentFuncExps.TryGetValue(exp, out res))
            {
                if (exp is ILocalDefinition)
                {
                    res = new NamedTypeTerm(exp as ILocalDefinition);
                }
                else if (exp is IThisReference)
                {
                    res = CreateFormalParameterTypeTerm(currentMethod, 0);
                }
                else if (exp is IBoundExpression)
                {
                    res = GetTypeTermForDefinition((exp as IBoundExpression).Definition);
                }
                else if (exp is IArrayIndexer)
                {
                    res = GetTypeTermForDefinition(exp);
                }
                else if (exp is IMethodCall)
                {
                    var call = exp as IMethodCall;
                    res = CreateReturnTypeTypeTerm(call.MethodToCall.ResolvedMethod);
                }
                else if (exp is ICompileTimeConstant)
                {
                    var ctc = exp as ICompileTimeConstant;
                    if (ctc.Value is string)
                    {
                        res = new ConstantStringTypeTerm(ctc.Value as string);
                    }
                    else
                    {
                        res = new ConstantTypeTerm(ctc.Value);
                    }
                }
                else if (exp is IConditional)
                {
                    var cond = exp as IConditional;
                    res = GetTypeTermFor(cond.ResultIfTrue);
                }
                else if (exp is ICreateObjectInstance
                    || exp is ICreateDelegateInstance
                    || exp is IConversion
                    || exp is ICastIfPossible
                    || exp is ITypeOf
                    || exp is ICheckIfInstance
                    || exp is IAddressOf
                    || exp is IAddressDereference
                    || exp is IUnaryOperation
                    || exp is IDefaultValue
                    || exp is IPopValue
                    || exp is IBinaryOperation)
                {
                    res = new AnonymousTypeTerm();
                }
                else if (exp is ICreateArray)
                {
                    // TODO Could make arrays related to the type they contain...
                    res = new AnonymousTypeTerm();
                }
                else
                {
                    res = new AnonymousTypeTerm();
                    /*throw new NotImplementedException("Unknown expression type: "
                        + exp.GetType());*/
                }
                currentFuncExps[exp] = res;
            }
            return res;
        }

        private static TypeConstraint ConstraintForAssignment(TypeTerm lvalue, TypeTerm rvalue)
        {
            return lvalue.IsAssignedFrom(rvalue);
        }
        private TypeConstraint ConstraintForAssignment(IExpression lvalue, IExpression rvalue)
        {
            return GetTypeTermFor(lvalue).IsAssignedFrom(GetTypeTermFor(rvalue));
        }
        private void RegisterConstraint(TypeConstraint c)
        {
            currentFuncConstraints.Add(c);
        }
        private void RegisterConstraints(IEnumerable<TypeConstraint> cs)
        {
            currentFuncConstraints.AddRange(cs);
        }


        public Dictionary<IdentifiedTypeTerm, TypeTerm> GetTermMeaningsWithout(IMethodDefinition func)
        {
            Queue<TypeConstraint> constraints
                = new Queue<TypeConstraint>(constraintsByFunc
                    .Where(kv => !kv.Key.Equals(func))
                    .Select(kv => kv.Value).Concat());

            // DEBUG
            /*
            Logger.printHeader("Constraints Found");
            foreach (var c in constraints)
            {
                Logger.println(c.ToString());
            }
            Logger.println();
             */

            Dictionary<IdentifiedTypeTerm, TypeTerm> termMeanings
                = constraints.Select(c => c.Left).Union(constraints.Select(c => c.Right))
                             .Where(tt => tt is IdentifiedTypeTerm)
                             .Cast<IdentifiedTypeTerm>()
                             .Distinct()
                             .ToDictionary(t => t, t => (TypeTerm)t);
            // Assert that every setters can be set by its own getter.
            foreach (var propertyConstraint in termMeanings.Keys
                .Select(tt => tt as FunctionSignatureTypeTerm)
                .Where(tt => tt != null)
                .Cast<FunctionSignatureTypeTerm>()
                .Where(tt => tt.IsPropertyGet || tt.IsPropertySet)
                .GroupBy(tt => tt.Method.ContainingType.ToString()
                    + "::" + tt.Method.Name.ToString().Substring(4))
                .Where(g => g.Count() == 2 && !g.ElementAt(0).Equals(g.ElementAt(1)))
                .Select(g => g.First(tt => tt is FormalParameterTypeTerm).IsAssignedFrom(g.First(tt => tt is ReturnTypeTypeTerm))))
            {
                constraints.Enqueue(propertyConstraint);
            }
            var disjointSets = new DisjointSets<TypeTerm>(
                new HashSet<TypeTerm>(
                    new IEnumerable<TypeTerm>[] {
                        termMeanings.Keys,
                        termMeanings.Values, 
                        constraints.Select(c => c.Left),
                        constraints.Select(c => c.Right),
                    }.Concat()));
            foreach (var c in constraints)
            {
                disjointSets.Union(c.Left, c.Right);
            }
            termMeanings = termMeanings.ToDictionary(kv => kv.Key,
                    kv => disjointSets.FindRoot(kv.Value));
            /*
            while (constraints.Any())
            {
                var curr = constraints.Dequeue();
                foreach (var c in constraints)
                {
                    c.Replace(curr.Left, curr.Right);
                }
                termMeanings = termMeanings.ToDictionary(kv => kv.Key,
                    kv => kv.Value.Replace(curr.Left, curr.Right));
            }*/
            return termMeanings;
        }
        public Dictionary<IdentifiedTypeTerm, TypeTerm> GetTermMeanings()
        {
            return GetTermMeaningsWithout(null);
        }

        public void DisplayTermMeanings()
        {
            var termMeanings = GetTermMeanings();

            /*
            Logger.printHeader("Computed Type Constraints");
            foreach(var kv in termMeanings)
            {
                Logger.println(kv.Key + " = " + kv.Value);
            }
            Logger.println();*/
            Logger.PrintHeader("Computed Type Groups");
            int groupNum = 0;
            foreach (var group in termMeanings
                .ToLookup(kv => kv.Value, kv => kv.Key)
                .Select(g => g.Key.Singleton()
                            .Union(g.Cast<TypeTerm>())
                            .Distinct()
                            .OrderBy(t => t.ToString()))
                .Where(g => g.Count(tt => tt is IdentifiedTypeTerm) > 1)
                .OrderByDescending(g => g.Count(tt => tt is IdentifiedTypeTerm)))
            {
                groupNum++;
                string groupStr = string.Join("\n", group
                    .Where(t => !(t is AnonymousTypeTerm))
                    .Select(t => groupNum + ": " + t.ToString()).ToArray());
                System.Console.WriteLine(groupStr);
                System.Console.WriteLine();
                Logger.PrintLine(groupStr);
                Logger.PrintLine();
            }
        }
    }
    class SavedExpression<E> where E : IExpression
    {
        private readonly IMethodDefinition containingMethod;
        private readonly E exp;
        private readonly List<TypeConstraint> inMethodConstraints;
        private readonly Dictionary<IExpression, TypeTerm> expressionTypeTerms;
        private readonly List<ILocalDefinition> locals;

        public SavedExpression(IMethodDefinition containingMethod,
                               E exp, List<TypeConstraint> constraints,
                               Dictionary<IExpression, TypeTerm> expressionTypeTerms,
                               List<ILocalDefinition> locals)
        {
            this.containingMethod = containingMethod;
            this.exp = exp;
            this.inMethodConstraints = constraints;
            this.expressionTypeTerms = expressionTypeTerms;
            this.locals = locals;
        }

        public E Expression { get { return exp; } }
        public IMethodDefinition ContainingMethod { get { return containingMethod; } }
        public IEnumerable<ILocalDefinition> Locals { get { return locals; } }

        public HashSet<TypeTerm> GetExpressionMeanings(IExpression exp)
        {
            TypeTerm expTypeTerm;
            var res = new HashSet<TypeTerm>();
            if (expressionTypeTerms.TryGetValue(exp, out expTypeTerm))
            {
                Queue<TypeTerm> terms = new Queue<TypeTerm>();
                terms.Enqueue(expTypeTerm);
                res.Add(expTypeTerm);

                while (terms.Count > 0)
                {
                    var term = terms.Dequeue();
                    foreach (var c in inMethodConstraints)
                    {
                        // TODO switch left/right?
                        if (c.Left.Equals(term) && !res.Contains(c.Right))
                        {
                            terms.Enqueue(c.Right);
                            res.Add(c.Right);
                        }
                        else if (c.Right.Equals(term) && !res.Contains(c.Left))
                        {
                            terms.Enqueue(c.Left);
                            res.Add(c.Left);
                        }
                    }
                }
            }
            return res;
        }
    }
    /*
    class SavedMethodCall : SavedExpression<IMethodCall>
    {

    }*/

    abstract class TypeTerm : System.IEquatable<TypeTerm>
    {
        public TypeTerm Replace(TypeTerm var, TypeTerm replacement)
        {
            if (this.Equals(var))
            {
                return replacement;
            }
            else
            {
                return this;
            }
        }
        public override abstract bool Equals(object obj);
        public override abstract int GetHashCode();
        public override abstract string ToString();

        public bool Equals(TypeTerm obj)
        {
            return this.Equals((object)obj);
        }
    }
    abstract class IdentifiedTypeTerm : TypeTerm
    { }
    class AnonymousTypeTerm : TypeTerm
    {
        private static int nextId = 0;
        private readonly int id;
        public AnonymousTypeTerm()
        {
            this.id = nextId++;
        }
        public override string ToString()
        {
            return "AnonymousTypeTerm(" + id + ")";
        }
        public override int GetHashCode()
        {
            return id.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is AnonymousTypeTerm && id == (obj as AnonymousTypeTerm).id;
        }
    }
    class NamedTypeTerm : TypeTerm
    {
        private readonly string name;
        public NamedTypeTerm(string name)
        {
            this.name = name;
        }
        public NamedTypeTerm(IMethodDefinition method, string name)
            : this(method.GetQualifiedName()
                + ":::" + name)
        { }
        public NamedTypeTerm(ILocalDefinition local)
            : this(local.MethodDefinition, local.Name.ToString())
        { }
        public override string ToString()
        {
            return "NamedTypeTerm(" + name + ")";
        }
        public override int GetHashCode()
        {
            return name.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is NamedTypeTerm && name.Equals((obj as NamedTypeTerm).name);
        }
    }
    class ConstantTypeTerm : TypeTerm
    {
        private readonly string str;
        public ConstantTypeTerm(object obj)
        {
            this.str = obj == null ? "null" : obj.ToString();
        }
        public override string ToString()
        {
            return "ConstantTypeTerm(" + str + ")";
        }
        protected string String { get { return str; } }
        public override int GetHashCode()
        {
            return str.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            // Identical constant strings are still not equal
            return this == obj;
        }
    }
    class ConstantStringTypeTerm : ConstantTypeTerm
    {
        public ConstantStringTypeTerm(string str)
            : base(str)
        { }
        public override string ToString()
        {
            return "ConstantStringTypeTerm(\"" + String + "\")";
        }
    }
    class NamedIdentifiedTypeTerm : IdentifiedTypeTerm
    {
        private readonly string name;
        public NamedIdentifiedTypeTerm(string name)
        {
            this.name = name;
        }
        public NamedIdentifiedTypeTerm(ITypeDefinition type, string name)
            : this(type.ToMangledString() + ":::" + name)
        { }
        public NamedIdentifiedTypeTerm(ITypeDefinitionMember mem)
            : this(mem.ContainingTypeDefinition, mem.Name.ToString())
        { }
        public override string ToString()
        {
            return "NamedIdentifiedTypeTerm(" + name + ")";
        }
        public override int GetHashCode()
        {
            return name.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is NamedIdentifiedTypeTerm
                && name.Equals((obj as NamedIdentifiedTypeTerm).name);
        }
    }
    abstract class FunctionSignatureTypeTerm : IdentifiedTypeTerm
    {
        private readonly IMethodDefinition func;

        public FunctionSignatureTypeTerm(IMethodDefinition func)
        {
            if (func.HasThisParameter() && !func.IsConstructor)
            {
                this.func = func.GetBaseOfOverride();
            }
            else
            {
                this.func = func;
            }
            if (func is Dummy)
            {
            }
        }

        public IMethodDefinition Method
        {
            get { return func; }
        }

        public virtual bool IsPropertySet
        {
            get { return false; }
        }
        public virtual bool IsPropertyGet
        {
            get { return false; }
        }
    }
    class FormalParameterTypeTerm : FunctionSignatureTypeTerm
    {
        private readonly int position;

        public FormalParameterTypeTerm(IMethodDefinition func, int position)
            : base(func)
        {
            this.position = position;
        }

        public override bool IsPropertySet
        {
            get { return position == 1 && Method.Name.ToString().StartsWith("set_"); }
        }

        public override string ToString()
        {
            return "FormalParameterTypeTerm(func="
                + Method.GetQualifiedName()
                + ", funcType=" + Method.GetMethodTypeString()
                + ", position=" + position + ")";
        }
        public override int GetHashCode()
        {
            return Method.GetHashCode() ^ position.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is FormalParameterTypeTerm
                && position == (obj as FormalParameterTypeTerm).position
                && Method.HasSameMethodName((obj as FormalParameterTypeTerm).Method);
        }
    }
    class ReturnTypeTypeTerm : FunctionSignatureTypeTerm
    {
        public ReturnTypeTypeTerm(IMethodDefinition func)
            : base(func)
        { }

        public override bool IsPropertyGet
        {
            get { return Method.Name.ToString().StartsWith("get_"); }
        }

        public override string ToString()
        {
            return "ReturnTypeTypeTerm(func=" + Method.GetQualifiedName()
                + ";type=" + Method.GetMethodTypeString() + ")";
        }
        public override int GetHashCode()
        {
            return Method.GetHashCode();
        }
        public override bool Equals(object obj)
        {
            return obj is ReturnTypeTypeTerm
                && Method.HasSameMethodName((obj as ReturnTypeTypeTerm).Method);
        }
    }

    static class TypeTermUtil
    {
        public static IdentifiedTypeTerm AsTypeTerm(this IFieldReference field)
        {
            return new NamedIdentifiedTypeTerm(field.ResolvedField);
        }
        public static IdentifiedTypeTerm AsTypeTerm(this IFieldDefinition field)
        {
            return new NamedIdentifiedTypeTerm(field);
        }
        public static IdentifiedTypeTerm AsTypeTerm(this ITypeDefinitionMember mem)
        {
            return new NamedIdentifiedTypeTerm(mem);
        }
    }

    class TypeConstraint
    {
        private TypeTerm left, right;
        public TypeConstraint(TypeTerm left, TypeTerm right)
        {
            this.left = left;
            this.right = right;
        }
        public TypeTerm Left { get { return left; } }
        public TypeTerm Right { get { return right; } }

        public override string ToString()
        {
            return Left + " = " + Right;
        }

        public void Replace(TypeTerm var, TypeTerm replacement)
        {
            left = left.Replace(var, replacement);
            right = right.Replace(var, replacement);
        }
    }
    static class TypeConstraintUtil
    {
        public static TypeConstraint IsAssignedFrom(this TypeTerm lvalue, TypeTerm rvalue)
        {
            return new TypeConstraint(lvalue, rvalue);
        }

        public static TypeTerm GetReturnTypeMeaning(
            this Dictionary<IdentifiedTypeTerm, TypeTerm> typeMeanings,
            IMethodDefinition func)
        {
            var retTT = new ReturnTypeTypeTerm(func);
            TypeTerm res;
            if (typeMeanings.TryGetValue(retTT, out res))
            {
                return res;
            }
            else
            {
                return retTT;
            }
        }
        public static TypeTerm GetParameterTypeMeaning(
            this Dictionary<IdentifiedTypeTerm, TypeTerm> typeMeanings,
            IMethodDefinition func,
            int position)
        {
            var paramTT = new FormalParameterTypeTerm(func, position);
            TypeTerm res;
            if (typeMeanings.TryGetValue(paramTT, out res))
            {
                return res;
            }
            else
            {
                return paramTT;
            }
        }

        /*
        public static TypeTerm GetArgumentTypeTerm(this IMethodCall instr, int pos)
        {
            return new NamedTypeTerm(instr
                            .SourceOperands
                            .AsEnumerable()
                            .Skip(1)
                            .Where(o => o.IsVariableOperand
                                || o.IsMemoryOperand)
                            .ElementAt(pos));
        }
         */
    }
}
